#pragma once
#include <mutex>

namespace LearnCPlusPlus
{
	template<class T>
	class CSharedPtr;

	template<class T, typename... Types>
	CSharedPtr<T> makeShared(Types&&... vArgs)	// Universal reference and Variadic template to get constructor's parameters
	{
		return CSharedPtr(new T(std::forward<Types>(vArgs)...));
	}

	template<class T>
	class CSharedPtr
	{
	public:
		CSharedPtr(const CSharedPtr<T>& vOther) { __atomicCopy(vOther); }

		~CSharedPtr() { __atomicMinus(); }

		// get, reset(deprecated, minus and init), swap, use_count
		T* get() const { return m_pObjectPtr; }

		void swap(CSharedPtr& vOther)
		{
			if (vOther == *this)
				return;

			std::lock_guard<std::mutex> Guard(m_Mutex);

			std::swap(m_pObjectPtr, vOther.m_pObjectPtr);
			std::swap(m_pRefCount, vOther.m_pRefCount);
		}

		int useCount() const { return m_pRefCount ? *m_pRefCount : 0; }

		void operator = (const CSharedPtr<T>& vOther)
		{
			if (vOther == *this)
				return;

			__atomicMinus();

			__atomicCopy(vOther);
		}

		bool operator == (const CSharedPtr<T>& vOther) const { return vOther.m_pObjectPtr == m_pObjectPtr; }

		// use
		T& operator * () const { return *m_pObjectPtr; }
		T* operator -> () const { return m_pObjectPtr; }

	private:
		CSharedPtr(T* vObjectPtr) { __atomicInit(vObjectPtr); }

		void __atomicInit(T* vObjectPtr)
		{
			std::lock_guard<std::mutex> Guard(m_Mutex);

			m_pObjectPtr = vObjectPtr;
			m_pRefCount = m_pObjectPtr ? new int(1) : nullptr;
		}

		void __atomicMinus()
		{
			if (m_pObjectPtr == nullptr)
				return;

			std::lock_guard<std::mutex> Guard(m_Mutex);

			(*m_pRefCount)--;
			if (*m_pRefCount == 0)
			{
				delete m_pObjectPtr;
				delete m_pRefCount;
			}
		}

		void __atomicCopy(const CSharedPtr<T>& vOther)
		{
			std::lock_guard<std::mutex> Guard(m_Mutex);

			m_pObjectPtr = vOther.m_pObjectPtr;
			m_pRefCount = vOther.m_pRefCount;
			if (m_pObjectPtr)
				(*m_pRefCount)++;
		}

		int* m_pRefCount = nullptr;
		T* m_pObjectPtr = nullptr;

		static std::mutex m_Mutex;

		template<class T, typename... Types>
		friend CSharedPtr<T> makeShared<T, Types>(Types&&... vArgs);
	};

	template<class T>
	std::mutex CSharedPtr<T>::m_Mutex;
}